<template>
	<div 
			class="list" 
			ref="listRef" 
			@dragstart="drag" 
			@dragover="dragover" 
			@dragend="dragend" 
			@mouseout="out"
	>
		<div class="list-item" 
			v-for="(item, index) in listData" 
			:key="index" 
			:orgIndex="index" 
			:draggable="isDrag"
			@click="click"
		>
			<slot :row="item"></slot>
		</div>
	</div>
</template>

<script>
	export default {
		name: 'DragList',
		props: {
			listData: Array, // list绑定的数据
			isDrag: Boolean, // 是否开启拖拽
			callDrop: Function,
			callClick: Function
		},
		data() {
			return {
				oldListInfo: [], // 拖拽前数据
				listInfo: [], // 拖拽后数据
				draging: null, // 被拖拽元素
				target: null, // 目标元素
				allow: false // 是否可拖拽，鼠标按下后才可拖拽
			}
		},
		created() {
			this.listInfo = this.listData;
		},
		methods: {
			click(e) {
				let listItem = this.getParentNode(e.target, "list-item");
				if (!listItem) return;
				let clickItemIndex = this.getIndex(listItem);
				// call 自定义click，返回被点击数据
				if (this.callClick && typeof(this.callClick) == 'function') {
					this.callClick({
						"index": clickItemIndex,
						"item": this.listInfo[clickItemIndex]
					});
				}
			},
			drag(e) {
				this.draging = e.target;
				this.allow = true;
				this.oldListInfo = this.listInfo;
			},
			dragover(e) {
				e.preventDefault();
				if (!this.allow) return;
				let listItem = this.getParentNode(e.target, "list-item");
				if (!listItem) return;

				this.target = listItem;

				let targetTop = this.target.getBoundingClientRect().top;
				let dragingTop = this.draging.getBoundingClientRect().top;

				let dragingIndex = this.getIndex(this.draging);
				let targetIndex = this.getIndex(this.target);

				if (this.target.className === "list-item" && this.target !== this.draging) {
					if (this.target) {
						if (this.target.animated) {
							return;
						}
					}
					// 互换位置
					if (dragingIndex < targetIndex) {
						this.target.parentNode.insertBefore(this.draging, this.target.nextSibling);
					} else {
						this.target.parentNode.insertBefore(this.draging, this.target);
					}
					this.animate(targetTop, this.target);
					this.animate(dragingTop, this.draging);
				}
			},
			dragend(e) {
				// 返回调整后的数据，调用外部自定义函数
				console.log(e)
				this.listInfo = this.getNewData();

				if (this.callDrop && typeof(this.callDrop) == 'function') {
					this.callDrop({
						"oldListInfo": this.oldListInfo,
						"newListInfo": this.listInfo
					});
				}
			},
			// 鼠标移出list
			out(e) {
				if (e.clientY < this.$refs.listRef.getBoundingClientRect().top || e.clientY > this.$refs.listRef
					.getBoundingClientRect().bottom) {
					this.allow = false;
				}
			},
			/*
			 * 按类名查询父元素
			 * node：当前节点
			 * pclassname：父元素类名
			 */
			getParentNode(node, pclassname) {
				let p = node;
				if (p.className == pclassname) return p;
				while (p.parentNode) {
					if (p.parentNode.className == pclassname) {
						return p.parentNode;
					} else {
						p = p.parentNode;
					}
					if (p.nodeName == "BODY") return 0;
				}
			},
			/*
			 * 获取元素在父元素的索引
			 * node：当前节点
			 * pclassname：父元素类名
			 */
			getIndex(el) {
				let domData = Array.from(this.$refs.listRef.childNodes);
				return domData.findIndex(i => i.innerText == el.innerText);
			},
			// 动画效果
			animate(startPos, dom) {
				let offset = startPos - dom.getBoundingClientRect().top;
				dom.style.transition = "none";
				dom.style.transform = `translateY(${offset}px)`;

				dom.offsetWidth;
				dom.style.transition = "transform .3s";
				dom.style.transform = "";
				clearTimeout(dom.animated);

				dom.animated = setTimeout(() => {
					dom.style.transition = "";
					dom.style.transform = "";
					dom.animated = false;
				}, 300)
			},
			// 获取拖拽后的数据
			getNewData() {
				let currentNodes = Array.from(this.$refs.listRef.childNodes);
				let listArr = [];
				currentNodes.map((i, index) => {
					return listArr[index] = this.listData[i.getAttribute("orgIndex")];
				});
				return listArr;
			}
		}
	}
</script>

<style scoped>
	.list {
		display: block;
	}

	.list-item {
		width: 100%;
		height: 100%;
		user-select: none;
		cursor: pointer;
	}
</style>
